<?php

global $IOFunctions;
$IOFunctions = include_once 'IO.php';
stream_wrapper_register("code", "CodeStream");

class CodeStreamFiber
{
    public static function __callStatic($method, $arguments)
    {
        $fiberMethod = "fiber_{$method}";
        if (is_callable($fiberMethod)) {
            return call_user_func_array($fiberMethod, $arguments);
        }
        return Fiber::suspend([$method, $arguments]);
    }
}

//https://www.php.net/manual/zh/class.streamwrapper.php
//https://www.php.net/manual/zh/tokens.php
//https://github.com/amphp/ext-fiber
class CodeStream
{
    private $position;
    private $code;

    public function code_by_tokens($tokens)
    {
        $code = "";
        foreach ($tokens as $token) {
            if (is_array($token)) {
                $code .= $token[1];
            } else {
                $code .= $token;
            }
        }
        return $code;
    }

    public function code_wrap_tokens()
    {
        return [
            [382, " ", 11],
            [323, "\"code://\"", 11],
            [382, " ", 11],
            ".",
            [382, " ", 11],
        ];
    }

    public function exists_wrap_tokens($function, $type = 'function_exists')
    {
        return [
            [327, "if", 16],
            "(",
            "!",
            [319, $type, 16],
            "(",
            $type == 'defined' ? [323, "{$function}", 16] : [323, "'{$function}'", 16],
            ")",
            ")",
            [382, " ", 16],
            "{",
            [382, "\n", 16],
        ];
    }

    public function code_filter($code, $file)
    {
        global $IOFunctions;
        $includeTokens = ['T_REQUIRE', 'T_REQUIRE_ONCE', 'T_INCLUDE', 'T_INCLUDE_ONCE'];
        $arraySkip = 0; //解决数组循环过程中新增数组成员的问题
        $tokens = token_get_all($code);
        // var_dump($tokens);
        // exit;
         // echo token_name(346); //function
        // echo token_name(323);
        // echo token_name(362);
        // exit;
        $inFunction = 0;
        $inClass = 0;
        $inDefine = 0;
        // var_dump($IOFunctions);exit;
        foreach ($tokens as $key => $token) {
            if (is_array($token)) {
                $tokenName = token_name($token[0]);

                //替换include语法，增加 code:// 包装器
                if (in_array($tokenName, $includeTokens)) {
                    $codeWrap = $this->code_wrap_tokens();
                    array_splice($tokens, $key + 1 + $arraySkip, 0, $codeWrap);
                    $arraySkip += count($codeWrap);
                }

                //IO阻塞函数统一替换
                if ('T_STRING' == $tokenName && in_array(strtolower($token[1]), $IOFunctions)) {
                    $tokens[$key + $arraySkip][1] = "CodeStreamFiber::{$token[1]}";
                }
                
                //防止常量重复定义
                if('T_STRING' == $tokenName && strtolower($token[1]) == 'define') {
                    $functionWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 2][1], 'defined');
                    array_splice($tokens, $key + $arraySkip, 0, $functionWrap);
                    $arraySkip += count($functionWrap);
                    $inDefine = 1;
                }

                //防止函数重复定义
                if ('T_FUNCTION' == $tokenName && $inClass == 0) {
                    // exists_wrap_tokens
                    $functionWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 2][1]);
                    array_splice($tokens, $key + $arraySkip, 0, $functionWrap);
                    $arraySkip += count($functionWrap);
                    $inFunction = 1;
                }

                //防止类重复定义
                if('T_CLASS' == $tokenName && $inClass == 0) {
                    $classWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 2][1], 'class_exists');
                    array_splice($tokens, $key + $arraySkip, 0, $classWrap);
                    $arraySkip += count($classWrap);
                    $inClass = 1;
                }

                //防止抽象类重复定义
                if('T_ABSTRACT' == $tokenName && is_array($tokens[$key + $arraySkip + 2]) && $tokens[$key + $arraySkip + 2][1] == 'class') {
                    $classWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 4][1], 'class_exists');
                    array_splice($tokens, $key + $arraySkip, 0, $classWrap);
                    $arraySkip += count($classWrap);
                    $inClass = 1;
                }

                //防止接口重复定义
                if('T_INTERFACE' == $tokenName) {
                    $classWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 2][1], 'interface_exists');
                    array_splice($tokens, $key + $arraySkip, 0, $classWrap);
                    $arraySkip += count($classWrap);
                    $inClass = 1;
                }

                if('T_TRAIT' == $tokenName) {
                    $classWrap = $this->exists_wrap_tokens($tokens[$key + $arraySkip + 2][1], 'trait_exists');
                    array_splice($tokens, $key + $arraySkip, 0, $classWrap);
                    $arraySkip += count($classWrap);
                    $inClass = 1;
                }
            } else {
                //class_exists 和 function_exists 的结束
                if ($inFunction) {
                    if ($token == '{') {
                        $inFunction++;
                    }
                    if ($token == '}') {
                        $inFunction--;
                        if ($inFunction == 1) {
                            $inFunction = 0;
                            $arraySkip += 1;
                            array_splice($tokens, $key + $arraySkip, 0, "}");
                        }
                    }
                }

                if($inClass) {
                    if ($token == '{') {
                        $inClass++;
                    }
                    if ($token == '}') {
                        $inClass--;
                        if ($inClass == 1) {
                            $inClass = 0;
                            $arraySkip += 1;
                            array_splice($tokens, $key + $arraySkip, 0, "}");
                        }
                    }
                }

                if($inDefine && $token == ';') {
                    $inDefine = 0;
                    $arraySkip += 1;
                    array_splice($tokens, $key + $arraySkip, 0, "}");
                }
            }
        }

        $code_file = realpath($file);
        $code_dirname = dirname($file);
        $fileTokens = ['T_DIR' => "\"{$code_dirname}\"", 'T_FILE' => "\"{$code_file}\""];
        foreach ($tokens as $key => $token) {
            if (is_array($token)) {
                $tokenName = token_name($token[0]);
                //__DIR__ 替换成 对应的字符串(323,12)
                if (isset($fileTokens[$tokenName])) {
                    $tokens[$key][0] = 323;
                    $tokens[$key][1] = $fileTokens[$tokenName];
                    $tokens[$key][2] = 12;
                }
            }
        }
        $code = $this->code_by_tokens($tokens);
        // var_dump($this->code_by_tokens($tokens));
        // exit;
        return $code;
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $url = parse_url($path);
        $file = $url["host"] . $url["path"];
        // echo "code {$url["host"]}\n";
        $code = file_get_contents($file, FILE_USE_INCLUDE_PATH);
        $this->code = $this->code_filter($code, $file);
        $this->position = 0;
        return true;
    }

    public function stream_read($count)
    {
        $p = &$this->position;
        $ret = substr($this->code, $p, $count);
        $p += strlen($ret);
        return $ret;
    }

    public function stream_write($data)
    {

    }

    public function stream_tell()
    {
        return $this->position;
    }

    public function stream_eof()
    {
        return $this->position >= strlen($this->code);
    }

    public function stream_seek($offset, $whence)
    {

    }

    public function stream_set_option($option, $arg1, $arg2)
    {

    }

    public function stream_stat()
    {
        return [];
    }
}
